Skip to Content

04. Memory 与 RAG 工程实践

本章高频面试题

  1. 什么是短期记忆,什么是长期记忆?
  2. 为什么历史对话不能简单等同于记忆系统?
  3. 什么是 RAG?它和搜索、向量检索、知识库分别是什么关系?
  4. 为什么工业级 RAG 不能只靠纯向量检索?
  5. 什么是 Hybrid Search?什么是 rerank?常用 reranker 怎么选?
  6. 什么是 Contextual Retrieval?它解决什么问题?
  7. Chunking、索引、过滤、重排各自解决什么问题?
  8. 什么是 Query transformation(HyDE、multi-query、decomposition)?
  9. 什么是 GraphRAG?什么时候值得上?
  10. 如何设计一个长期运行 Agent 的记忆系统?写入策略怎么设计?
  11. 如何评估一个 RAG 系统是否真的有效?RAGAS 等工具怎么用?
  12. Agentic RAG 和 2-Step RAG 有什么区别?
  13. 多租户 RAG 系统的隔离怎么做?

1. 什么是短期记忆

短期记忆通常指:

当前会话、当前线程、当前任务在运行中需要保留的工作记忆。

它通常包括:

  • 最近几轮消息
  • 当前任务状态
  • 已完成步骤
  • 上一步工具结果
  • 临时中间产物

短期记忆的特点是:

  • 生命周期较短
  • 强依赖当前任务
  • 对连续执行很重要

LangChain / LangGraph 官方文档通常把这类内容称为 stateshort-term memory,承载在 checkpointer(MemorySaver、PostgresSaver 等)里。

2. 什么是长期记忆

长期记忆通常指:

跨会话、跨线程、跨时间仍然有复用价值的信息。

例如:

  • 用户偏好
  • 历史决策
  • 组织结构信息
  • 某类任务的稳定结论
  • 长期积累的业务事实

它的特点是:

  • 生命周期更长
  • 不一定在每次调用里都要出现
  • 需要按需检索

3. 为什么历史对话不等于记忆系统

很多初学者会觉得:

“我把聊天记录全存起来,不就有记忆了吗?”

这只解决了”保存”,没有解决”可用”。

真正的记忆系统至少还要解决:

  • 哪些内容值得长期保存(写入门槛)
  • 如何去重和合并
  • 如何按需检索
  • 过时信息怎么遗忘
  • 冲突信息怎么处理

所以:

对话日志是原材料,不是成熟的记忆系统。

4. 什么是 RAG

RAG 是 Retrieval-Augmented Generation,通常翻译为”检索增强生成”。

它的核心思想是:

  1. 先从外部知识源召回相关信息
  2. 再把这些信息提供给模型
  3. 最后让模型基于这些信息生成答案

RAG 之所以重要,是因为大模型有两个天然限制:

  • 训练知识是静态的
  • 上下文窗口是有限的(而且还会退化,见第 3 章的 context rot)

5. RAG 里的几个核心概念

5.1 Retrieval

“找到相关信息”的过程。RAG 的第一步。

5.2 Chunking

大文档不能直接整篇送进向量库或模型,因此要先切块。

切块目标是平衡两件事:

  • 每块足够完整,保留语义
  • 每块不要太大,便于精确召回

5.3 Embedding

把文本映射成向量表示。向量检索基于这些向量做语义相似搜索。

5.4 Rerank

初步召回的结果不一定最适合最终回答。Rerank 在召回后再做一次更细的排序,提升前几条结果的质量。

常用 reranker 类型:

  • Cross-encoder:BGE-reranker、Jina rerank 等开源方案,本地部署友好
  • 商业 rerank API:Cohere Rerank v3、Voyage rerank,效果通常更好但有延迟/成本
  • LLM-as-reranker:直接让小 LLM 对 top-k 做 pairwise 或 listwise 排序,灵活但贵
  • ColBERT / late interaction:token 级交互,质量高但工程复杂

生产上最常见的组合:hybrid 召回 top-100 → cross-encoder rerank 到 top-10。

5.5 Citation

很多生产 RAG 系统不只要回答,还要给出处。检索片段必须带 source metadata(doc_id、url、page、section、权限标签),否则没法做引用、过滤和审计。

6. 为什么工业级 RAG 不能只靠纯向量检索

纯向量检索擅长语义相似,但它有几个天然短板:

  1. 对精确关键词不稳定
  2. 对编号、版本号、报错码、SKU、法律条文编号不够敏感
  3. 容易召回”意思差不多但不是你要的”内容

这就是为什么工业界几乎都需要 Hybrid Search。

Hybrid Search 就是把:

  • 稀疏检索(BM25 关键词检索)
  • 稠密检索(向量检索)

结合起来使用。

现实查询通常同时包含两类信号:

  • 语义信号
  • 字面信号

举个例子:

“为什么订单 ORD-9281 的状态是 DELIVERED,但退款单 RF-201 还没关闭?”

ORD-9281 / DELIVERED / RF-201 都是强字面信号。如果只靠向量检索,容易召回泛化的退款说明文档。加上 BM25 或关键词过滤,命中稳定得多。

合并策略常用 RRF (Reciprocal Rank Fusion):不依赖分数归一化,按两个排序列表的排名合并,工程上极简单且鲁棒。

8. Contextual Retrieval:让 chunk 带上上下文再嵌入

这是 Anthropic 在 2024 年 9 月公开的方法,已成为高质量 RAG 的事实基线。

8.1 解决的问题

传统 chunking 会丢失上下文。例如一段写着”营收增长了 3%“的 chunk,脱离所在文档后,模型不知道是哪家公司、哪个季度。

8.2 做法

在 embed 和索引之前,用 LLM 为每个 chunk 生成一段 50-100 token 的上下文简介,然后把”context + 原 chunk”一起送进 embedding 和 BM25 索引。用的 prompt 大意是:

“Please give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval.”

8.3 Anthropic 报告的效果

  • 仅 Contextual Embeddings:失败率降低 35%(5.7% → 3.7%)
  • Contextual Embeddings + Contextual BM25:降低 49%(→ 2.9%)
  • 再叠加 rerank:降低 67%(→ 1.9%)

8.4 工程注意

  • 为每个 chunk 调一次 LLM 生成 context 会很贵——配合 Anthropic 的 prompt caching 可以把成本压到 1/10 左右(参见第 3 章)
  • Context 生成一次即可(离线预处理时做),不是每次查询都跑
  • 对已有 RAG 系统来说这是最 ROI 高的升级之一

9. Query Transformation

原始 query 不一定是”最适合检索的那个 query”,常见的改写策略:

  • HyDE (Hypothetical Document Embeddings):让模型先假想一份答案,用假想答案的 embedding 去检索,比直接用问题好
  • Multi-query:用 LLM 生成多个改写,并行检索后合并去重
  • Query decomposition:复杂问题拆成子问题分别检索(特别适合 multi-hop)
  • Step-back question:抽象问题到更高层再检索背景知识

这些都是”用 LLM 的成本换检索质量”的取舍,在高价值场景(法律、医疗、客服知识库)值得开启。

10. GraphRAG:什么时候值得上

Microsoft 2024 年发布的 GraphRAG 把文档组织成实体-关系图,并为社区生成 hierarchical summary,适合回答”全局性问题”(“整个文档集中关于 X 的主要观点是什么”),而不是”局部事实”(“X 在第几页说了什么”)。

工程取舍:

  • 上 GraphRAG 的前提:你的问题确实是 cross-document synthesis 型,不是 lookup 型
  • 成本不低:构建图和 community summary 需要对整个语料跑多次 LLM
  • 增量更新难:文档变了要重算受影响的子图
  • 多数企业场景不需要:普通 hybrid + rerank + contextual retrieval 已经够用

11. RAG 的几种典型模式

11.1 2-Step RAG

最经典的模式:检索 → 生成。

优点:简单、成本低、延迟可控。 适合:FAQ、企业知识问答、文档助手。

11.2 Agentic RAG

模型不只是被动接收检索结果,而是能主动决定:

  • 先查哪个数据源
  • 是否要二次检索
  • 是否要换关键词
  • 是否需要调用其他工具

优点:灵活、适合复杂任务。 缺点:成本高、更难控、更依赖评估和 guardrails。

11.3 Self-querying / Metadata routing

让 LLM 先解析用户 query 生成结构化过滤条件(例如”2024 年 Q3 之后的财报”→ date >= 2024-10-01),再带过滤执行向量检索。对时间、作者、状态这类结构化维度特别有用。

12. 长期记忆的存储设计

实用分法是三层:

12.1 事实型记忆

例如:用户偏好、固定配置、身份和组织关系。 适合结构化存储:KV、SQL、文档存储。

12.2 情节型记忆

例如:某次任务做了什么、出现了什么问题、最终怎么解决。 适合文档块 + 时间索引 + 向量检索。

12.3 关系型记忆

例如:用户属于哪个项目组、某服务依赖哪个系统、某审批流程需要哪些人参与。 适合图数据库或至少维护关系表。

LangGraph 官方把”long-term memory”抽成 store,可以挂不同 backend(Postgres、Redis 等)。这是一个实用抽象:让 runtime 不关心记忆的具体存储。

13. 长期记忆的更新策略:写入门槛才是关键

不是每轮对话都应该无脑入库。生产系统里写入策略往往比读取策略更重要。

常见做法:

  1. 候选记忆抽取:LLM 从对话里抽出”可能值得记住的事实”
  2. 价值判断 / write gate:用分类模型或 LLM-judge 判断是否真的值得入库(大多数对话不值得)
  3. 去重:向量相似度 + 字段级比对
  4. 合并:同主题多条 → 一条稳定版本
  5. 打分:重要性、置信度、时效性
  6. 入库:带 provenance(来源消息 id、时间)

13.1 写入模式

  • Hot-path write:对话中实时写入。延迟低但容易污染记忆库
  • Background reflection:异步 job 定期回顾最近对话决定写什么(Reflexion、Generative Agents 里用的方式)
  • Event-driven write:特定事件(任务完成、审批通过、用户明确要求记住)才触发写入

生产上建议以 event-driven + background reflection 为主,hot-path 只用于用户明确要求的”请记住…”。

13.2 遗忘机制

长期记忆如果没有遗忘和降权,会越来越脏。建议至少考虑:

  • 过时信息降权(decay(age)
  • 低命中记忆降权(长期没被召回就边缘化)
  • 低置信度记忆待确认(标记 needs_verification
  • 冲突记忆保留版本和时间戳(不要覆盖,追加)

14. RAG 系统的方法论

14.1 先确认知识源

不要一上来就”全量建向量库”。先判断你的知识来自哪里:

  • PDF / 文档系统 / Wiki
  • SQL / CRM / API
  • 代码库 / Issue tracker

很多时候最好的方案不是重建知识库,而是直接把已有系统作为 Tool——让 Agent 调 SQL 或 API,比提前抽取索引更新鲜、更精确。

14.2 先做 Retrieval Baseline

实用步骤是先做最简单可测的 baseline:

  1. 文档切块
  2. 建索引
  3. top-k 召回
  4. 模型整合回答

然后再逐步加入:

  • metadata filter
  • hybrid search(向量 + BM25 + RRF)
  • rerank(cross-encoder 或商业 API)
  • contextual retrieval(Anthropic 方案)
  • query rewrite / HyDE / multi-query
  • multi-hop retrieval

每加一层都要跑评估,确认真的有提升。

14.3 切块策略要按文档类型设计

不同文档类型的切块策略应该不同:

  • 法律合同:按条款和标题切,保留层级
  • 技术文档:按章节和代码块切,不要切断代码
  • FAQ:按问答对切
  • 聊天记录:按轮次或主题切
  • 代码:按函数/类切,保留 import 和签名

不要一套固定 chunk size 打天下。通用的参考范围:500-1500 tokens + 10-20% overlap。

14.4 给每个 chunk 补 metadata

尽量保留:

  • 标题、来源、时间、文档类型、作者、版本、权限标签
  • 如果做了 contextual retrieval,保留 generated context 字段

这些字段对过滤、排序和多租户隔离都很重要。

15. TypeScript 示例:Hybrid + Rerank 的检索编排

type SearchResult = { id: string; content: string; contextualHeader?: string; // 来自 contextual retrieval source: string; metadata: Record<string, unknown>; lexicalScore?: number; semanticScore?: number; finalScore?: number; }; export async function retrieveKnowledge( query: string, opts: { topK?: number; rerankTopK?: number; filters?: Record<string, unknown>; workspaceId: string; // 强制多租户过滤 } ): Promise<SearchResult[]> { const recallK = 100; const topK = opts.rerankTopK ?? 10; const baseFilter = { ...opts.filters, workspace_id: opts.workspaceId, // 一定要在存储层强制 }; const [vectorResults, keywordResults] = await Promise.all([ semanticSearch(query, recallK, baseFilter), keywordSearch(query, recallK, baseFilter), ]); const fused = rrfMerge(vectorResults, keywordResults); const reranked = await rerank(query, fused.slice(0, 50)); return reranked.slice(0, topK); } function rrfMerge(a: SearchResult[], b: SearchResult[]): SearchResult[] { const k = 60; const scores = new Map<string, { item: SearchResult; score: number }>(); const add = (list: SearchResult[]) => { list.forEach((item, rank) => { const prev = scores.get(item.id); const add = 1 / (k + rank + 1); if (prev) { prev.score += add; } else { scores.set(item.id, { item, score: add }); } }); }; add(a); add(b); return [...scores.values()] .sort((x, y) => y.score - x.score) .map((entry) => ({ ...entry.item, finalScore: entry.score })); } declare function semanticSearch( q: string, k: number, f: Record<string, unknown> ): Promise<SearchResult[]>; declare function keywordSearch( q: string, k: number, f: Record<string, unknown> ): Promise<SearchResult[]>; declare function rerank(q: string, r: SearchResult[]): Promise<SearchResult[]>;

几个工程要点体现在这段代码里:

  • Hybrid 召回用 RRF 融合,不依赖分数归一化
  • 多租户过滤在存储层强制,不依赖 app 层记得传
  • Rerank 前先用 RRF 筛到合理规模(50-100)
  • 返回前 topK(通常 5-10),避免上下文爆炸

16. 如何评估 RAG 是否有效

不能只看”模型回答看起来不错”。建议分三层评估。

16.1 检索层(retriever-level)

  • Recall@k、Precision@k
  • MRR / nDCG
  • 空召回率

构造评估集:从真实流量里采样 query → 人工标注 gold chunks。规模 50-200 条起。

16.2 生成层(generator-level)

  • Faithfulness:回答是否被证据支持(有没有幻觉)
  • Answer relevance:回答是否回应了用户问题
  • Context precision / recall:检索到的 chunks 是否真的被用上

开源工具链:

  • RAGAS:最主流的 RAG evals 框架,实现上面这些指标
  • TruLens:feedback function 框架,评估更灵活
  • LangSmith Evals:和 LangChain 生态集成紧
  • Phoenix (Arize):开源 LLM observability + evals

16.3 业务层

  • 是否解决用户问题
  • 是否降低人工成本
  • 是否缩短处理时间
  • 是否减少升级工单数

业务指标才是决定 RAG 是否真的”有用”的最终标准。

17. 多租户 RAG 的隔离

如果 Agent 系统是多租户 SaaS,RAG 层必须做硬隔离:

  • 存储层强制过滤:索引写入时必须带 workspace_id/tenant_id,查询时必须带 filter,不能只靠 app 层记得传
  • Per-tenant namespace:pgvector 可以用 schema 或 partition,Pinecone/Weaviate 用 namespace
  • 权限级 chunk filter:不只是租户,还有租户内的 ACL(某文档只有部门 X 能读)
  • 检索返回前二次校验:返回的 chunks 如果 metadata 与当前 principal 权限不符,丢弃并报警
  • 审计:检索日志必须能回答”谁在什么时间检索了哪些 chunks”

这套隔离在 SaaS 里是合规红线,不是 nice-to-have。

18. 本章方法论小结

  1. 历史对话不等于成熟的记忆系统
  2. 短期记忆服务当前任务,长期记忆服务跨会话复用;写入门槛比读取能力更关键
  3. RAG 的核心是”在生成前拿到更对的外部信息”
  4. 工业级 RAG 通常是 hybrid retrieval(向量+BM25+RRF)+ rerank + contextual retrieval 三件套
  5. Query transformation(HyDE / multi-query / decomposition)在高价值场景值得上
  6. GraphRAG 适合 cross-document synthesis 型问题,大多数场景不需要
  7. 不同文档类型需要不同 chunking 策略,chunk metadata 必须完整
  8. 长期记忆需要抽取、合并、去重、降权、遗忘;写入以 event-driven + background reflection 为主
  9. RAG 评估必须分检索层 / 生成层 / 业务层,用 RAGAS / TruLens / LangSmith evals 自动化
  10. 多租户隔离必须在存储层强制,不依赖 app 层规矩
  11. 最好的知识接入方式不一定是建向量库,也可能是把已有数据系统当 Tool
Last updated on